/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.integration.exporter;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.support.ExternalDependency;
import com.eviware.soapui.model.project.ProjectFactoryRegistry;
import com.eviware.soapui.support.SoapUIException;
import org.apache.commons.io.FileUtils;
import org.apache.xmlbeans.XmlException;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * Project exporting means that copy of existing project with copies of all
 * external dependencies will be put in one directory and packed together.
 * <p/>
 * Project copy will resolve all its dependency to dependency copies. Paths have
 * to be relative to the project file.
 *
 * @author robert
 */
public class ProjectExporter {

    private static final int BUFFER = 1024;
    private WsdlProject project;
    private WsdlProject projectCopy;
    private final int TEMP_DIR_ATTEMPTS = 10000;
    private File tmpDir;

    public ProjectExporter(WsdlProject project) {
        this.project = project;
    }

    /**
     * Creates packed project on given path
     *
     * @param exportPath
     * @return
     * @throws SoapUIException
     * @throws XmlException
     * @throws IOException
     */
    public boolean exportProject(String exportPath) throws IOException, XmlException, SoapUIException {

        boolean result = false;
        if ((tmpDir = createTemporaryDirectory()) != null) {
            if (createProjectCopy()) {
                if (copyDependencies()) {
                    projectCopy.setResourceRoot("${projectDir}");
                    projectCopy.save();
                    if (packageAll(exportPath)) {
                        result = true;
                    }
                }
            }
            deleteDir(tmpDir);
        }
        return result;
    }

    /**
     * Compress temporary directory and save it on given path.
     *
     * @param exportPath
     * @return
     * @throws IOException
     */
    private boolean packageAll(String exportPath) {
        if (!exportPath.endsWith(".zip")) {
            exportPath = exportPath + ".zip";
        }

        BufferedInputStream origin = null;
        ZipOutputStream out;
        boolean result = true;
        try {
            FileOutputStream dest = new FileOutputStream(exportPath);
            out = new ZipOutputStream(new BufferedOutputStream(dest));
            byte data[] = new byte[BUFFER];
            // get a list of files from current directory

            String files[] = tmpDir.list();

            for (int i = 0; i < files.length; i++) {
                //				System.out.println( "Adding: " + files[i] );
                FileInputStream fi = new FileInputStream(new File(tmpDir, files[i]));
                origin = new BufferedInputStream(fi, BUFFER);
                ZipEntry entry = new ZipEntry(files[i]);
                out.putNextEntry(entry);
                int count;
                while ((count = origin.read(data, 0, BUFFER)) != -1) {
                    out.write(data, 0, count);
                }
                origin.close();
            }
            out.close();
        } catch (IOException e) {
            // TODO: handle exception
            result = false;
            SoapUI.logError(e, "Error packaging export");
        }
        return result;
    }

    public static void unpackageAll(String archive, String path) {
        try {
            BufferedOutputStream dest = null;
            FileInputStream fis = new FileInputStream(archive);
            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                int count;
                byte data[] = new byte[BUFFER];
                // write the files to the disk
                FileOutputStream fos = new FileOutputStream(path + File.separator + entry.getName());
                dest = new BufferedOutputStream(fos, BUFFER);
                while ((count = zis.read(data, 0, BUFFER)) != -1) {
                    dest.write(data, 0, count);
                }
                dest.flush();
                dest.close();
            }
            zis.close();
        } catch (Exception e) {
            SoapUI.logError(e);
        }
    }

    public static List<String> getZipContents(String archive) {
        List<String> contents = new ArrayList<String>();

        try {
            ZipFile zipFile = new ZipFile(archive);
            for (Enumeration<? extends ZipEntry> em1 = zipFile.entries(); em1.hasMoreElements(); ) {
                contents.add(em1.nextElement().toString());
            }
        } catch (ZipException ze) {
            SoapUI.logError(ze);
        } catch (IOException e) {
            SoapUI.logError(e);
        }

        return contents;
    }

    /**
     * Do actual dependency copying and update project's copy dependency path.
     *
     * @throws IOException
     */
    private boolean copyDependencies() throws IOException {
        boolean result = true;
        projectCopy.setResourceRoot("${projectDir}");
        List<ExternalDependency> dependencies = projectCopy.getExternalDependencies();
        for (ExternalDependency dependency : dependencies) {
            switch (dependency.getType()) {
                case FILE:
                    File originalDependency = new File(dependency.getPath());
                    if (originalDependency.exists()) {
                        File targetDependency = new File(tmpDir, originalDependency.getName());
                        FileUtils.copyFile(originalDependency, targetDependency);
                        dependency.updatePath(targetDependency.getPath());
                    } else {
                        SoapUI.log.warn("Do not exists on local file system [" + originalDependency.getPath() + "]");
                    }
                    break;
                case FOLDER:
                    originalDependency = new File(dependency.getPath());
                    File targetDependency = new File(tmpDir, originalDependency.getName());
                    targetDependency.mkdir();
                    FileUtils.copyDirectory(originalDependency, targetDependency, false);
                    dependency.updatePath(targetDependency.getPath());
                    break;
                default:
                    break;
            }
        }

        return result;
    }

    /**
     * Creates project copy and save it in temporary directory. Set copy's
     * project path and resource root to ${projectDir}
     *
     * @return
     * @throws IOException
     * @throws SoapUIException
     * @throws XmlException
     */
    private boolean createProjectCopy() throws IOException, XmlException, SoapUIException {
        project.saveIn(new File(tmpDir, project.getName() + "-soapui-project.xml"));

        projectCopy = (WsdlProject) ProjectFactoryRegistry.getProjectFactory("wsdl").createNew(
                new File(tmpDir, project.getName() + "-soapui-project.xml").getAbsolutePath());//new WsdlProject( new File( tmpDir, project.getName() + ".xml" ).getAbsolutePath() );

        return projectCopy != null;
    }

    /**
     * Creates temporary directory where package will be created
     *
     * @return if operation is successuful
     */
    private File createTemporaryDirectory() {
        File baseDir = new File(System.getProperty("java.io.tmpdir"));
        String baseName = System.currentTimeMillis() + "-";

        for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
            File tempDir = new File(baseDir, baseName + counter);
            if (tempDir.mkdir()) {
                return tempDir;
            }
        }
        return null;
    }

    private boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }

        // The directory is now empty so delete it
        return dir.delete();
    }
}
